package k8;

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.logging.LogManager;
import java.util.logging.Logger;

import javax.imageio.ImageIO;

import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GLContext;

import org.lwjgl.opengl.GL11;

public final class k8
{
	// Version of k8
	public static final String VERSION = "k8 0.01";

	// The Logger instance accessible to all
	public static Logger logger = null;
	
	private static boolean finished;

	private static int frame;

	/** Prevents an instance of Application */
	private k8()
	{
	}

	/**
	 * Initialises the application.
	 * 
	 * @throws Exception
	 */
	public static void initialise(String title, int width, int height)
			throws Exception
	{
		DisplayMode chosenMode = null;
		int targetWidth = 800;
		int targetHeight = 600;

		LogManager logManager = LogManager.getLogManager();
		logManager.readConfiguration();
		logger = Logger.getLogger("");
		
		try
		{
			DisplayMode[] modes = Display.getAvailableDisplayModes();

			for (int i = 0; i < modes.length; i++)
			{
				if ((modes[i].getWidth() == targetWidth)
						&& (modes[i].getHeight() == targetHeight))
				{
					chosenMode = modes[i];
					break;
				}
			}
		} catch (LWJGLException e)
		{
			throw new Exception("Unable to determine display modes.");
		}

		// If we have no mode there was no appropriate
		if (chosenMode == null)
		{
			throw new Exception("Unable to find appropriate display mode.");
		}

		try
		{
			Display.setDisplayMode(chosenMode);
			Display.setTitle(title);
			Display.create();
		} catch (LWJGLException e)
		{
			throw new Exception("Unable to create display.");
		}

		// Enable texture mapping
		GL11.glEnable(GL11.GL_TEXTURE_2D);

		// Enable smooth shading
		GL11.glShadeModel(GL11.GL_SMOOTH);

		// Set the clear colour to something off-black so we can see colourless
		// objects while testing
		GL11.glClearColor(0.3f, 0f, 0f, 0f);

		// Enable depth testing
		GL11.glClearDepth(1.0f);
		GL11.glEnable(GL11.GL_DEPTH_TEST);
		GL11.glDepthFunc(GL11.GL_LEQUAL);

		// Enable alpha testing to discard fragments with low alpha
		GL11.glAlphaFunc(GL11.GL_GEQUAL, 0.4f);
		GL11.glEnable(GL11.GL_ALPHA_TEST);

		// Enable blending
		GL11.glEnable(GL11.GL_BLEND);
		GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);

		// Enable polygon offsets
		GL11.glEnable(GL11.GL_POLYGON_OFFSET_FILL);

		// Enable back face culling
		GL11.glEnable(GL11.GL_CULL_FACE);
		GL11.glCullFace(GL11.GL_BACK);

		// Select the modelview matrix
		GL11.glMatrixMode(GL11.GL_MODELVIEW);

		// Reset the modelview matrix
		GL11.glLoadIdentity();

		// Enable vertex and texture coordinate arrays
		GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
		GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
		GL11.glEnableClientState(GL11.GL_COLOR_ARRAY);

		// Initialise the running state
		finished = false;

		// Log some info about the environment
		k8.logger.info("k8 version                 : " + k8.VERSION);
		k8.logger.info("LWJGL version              : " + Sys.getVersion());
		k8.logger.info("OpenGL version             : " + GL11.glGetString(GL11.GL_VERSION));
		k8.logger.info("GL_ARB_vertex_buffer_object: " + GLContext.getCapabilities().GL_ARB_vertex_buffer_object);
	}

	/**
	 * Sets the window icon.
	 * 
	 * @param icon
	 *            URL of icon image
	 */
	public static void setIcon(URL icon)
	{
		if (icon == null)
			return;
		BufferedImage image = null;
		try
		{
			image = ImageIO.read(icon);
		} catch (IOException e)
		{
			k8.logger.warning(e.getMessage());
			return;
		}
		byte[] data = ((DataBufferByte) image.getRaster().getDataBuffer())
				.getData();
		ByteBuffer b[] = new ByteBuffer[3];
		b[0] = ByteBuffer.allocateDirect(data.length);
		b[0].order(ByteOrder.nativeOrder());
		b[0].put(data, 0, data.length);
		b[1] = ByteBuffer.allocateDirect(data.length);
		b[1].order(ByteOrder.nativeOrder());
		b[1].put(data, 0, data.length);
		b[2] = ByteBuffer.allocateDirect(data.length);
		b[2].order(ByteOrder.nativeOrder());
		b[2].put(data, 0, data.length);
		Display.setIcon(b);
	}

	/** Updates registered Logics and draws triangles until finish() called. */
	public static void run()
	{
		while (!finished)
		{
			// Update the display
			Display.update();

			// Check for close requests
			if (Display.isCloseRequested())
			{
				finished = true;
			}

			// The window is in the foreground, so we should play the game
			else if (Display.isActive())
			{
				Logic.updateAll();
				TexturedTriangles.updateAll();
				frame++; // Update frame count
			}

			// The window is not in the foreground, so we can allow other stuff
			// to run and infrequently update
			else
			{
				try
				{
					Thread.sleep(100);
				} catch (InterruptedException e)
				{
				}
				Logic.updateAll();

				// Only bother rendering if the window is visible or dirty
				if (Display.isVisible() || Display.isDirty())
				{
					TexturedTriangles.updateAll();
					frame++; // Update frame count
				}
			}
		}
		destroy();
	}

	/** Destroys all allocated resources. */
	public static void destroy()
	{
		Texture.destroyAll();
		TexturedTriangles.destroyAll();
		Display.destroy();
		System.exit(0);
	}

	/** Gets current frame count before resetting to 0 */
	public static int frame()
	{
		int temp = frame;
		frame = 0;
		return temp;
	}

	/** Flags application to finish. */
	public static void finish()
	{
		finished = true;
	}

}
